package com.tqz.jvm.cicuitbreaker;

import java.util.function.Function;
import java.util.function.Supplier;

/**
 * <p>
 *
 * @author tianqingzhao
 * @since 2022/10/20 20:26
 */
public class CircuitBreaker {
    
    private State state;
    
    private Config config;
    
    private Counter counter;
    
    private long lastOpenedTime;
    
    public CircuitBreaker(Config config) {
        this.counter = new Counter(config.getFailureCount(), config.getFailureTimeInterval());
        this.state = State.CLOSED;
        this.config = config;
    }
    
    public <T> T run(Supplier<T> toRun, Function<Throwable, T> fallback) {
        try {
            if (state == State.OPEN) {
                // 判断halfOpen是否超时
                if (halfOpenTimeout()) {
                    return halfOpenHandle(toRun, fallback);
                }
                
                return fallback.apply(new DegradeException("degrade by circuit breaker"));
            } else if (state == State.CLOSED) {
                T result = toRun.get();
                closed();
                return result;
            } else {
                return halfOpenHandle(toRun, fallback);
            }
        } catch (Exception e) {
            counter.incrFailureCount();
            // 错误次数达到阈值，进入open状态
            if (counter.failureThresholdReached()) {
                open();
            }
            return fallback.apply(e);
        }
    }
    
    private <T> T halfOpenHandle(Supplier<T> toRun, Function<Throwable, T> fallback) {
        try {
            // closed 状态超时进入 half_open 状态
            halfOpen();
            
            T result = toRun.get();
            int halfOpenSuccessCount = counter.incrSuccessHalfOpenCount();
            if (halfOpenSuccessCount >= this.config.getHalfOpenSuccessCount()) {
                // half_open 状态成功次数达到阈值，进入 closed 状态
                closed();
            }
            
            return result;
        } catch (Exception e) {
            // half_open 状态发生一次错误进入 open 状态
            open();
            return fallback.apply(new DegradeException("degrade by circuit breaker"));
        }
    }
    
    private void open() {
        state = State.OPEN;
        lastOpenedTime = System.currentTimeMillis();
    }
    
    private void closed() {
        counter.reset();
        state = State.CLOSED;
    }
    
    private void halfOpen() {
        state = State.HALF_OPEN;
    }
    
    private boolean halfOpenTimeout() {
        return System.currentTimeMillis() - lastOpenedTime > config.getHalfOpenTimeout();
    }
    
}
